""" Bewilder: An HTML generator

Copyright 2010 Brian Goldman

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import cgi
import functools
import threading

newline = '\n'
tab = '\t'
doctype = '<!DOCTYPE html>'

def builder(inner):
    @functools.wraps(inner)
    def outer(*ps, **kw):
        try:
            inner(*ps, **kw)
            return document.render()
        finally:
            document.reset()
    return outer

def text(s, escape=True):
    if escape:
        document.add([cgi.escape(s)])
    else:
        document.add([s])
        
class ElementMaker(object):
    def __getattr__(self, name):
        return Element(name)
    
    @staticmethod
    def create(namespace, names):
        if isinstance(names, basestring):
            names = names.split()
        for name in names:
            namespace[name] = Element(name)

elements = ElementMaker()

class Document(threading.local):
    def __init__(self):
        self.reset()
        
    def reset(self):
        self.lines = [(0, [doctype])]
        self.indent = 0
    
    def add(self, line):
        self.lines.append((self.indent, line))
        
    def render(self):
        lines = (indent * tab + ''.join(line) for indent, line in self.lines)
        return newline.join(lines)

document = Document()

class Element(object):
    def __init__(self, name):
        self.name = name.rstrip('_')

    def start(self, attrs=None):
        if attrs:
            attrs = ('{0}="{1}"'.format(k, cgi.escape(v,1)) for k,v in attrs)
            attrs = ' '.join(attrs)
            return '<{0} {1}>'.format(self.name, attrs)
        else:
            return '<{0}>'.format(self.name)
        
    def end(self):
        return '</{0}>'.format(self.name)
        
    def __call__(self, text='', **attrs):
        attrs = [(k.rstrip('_'), v) for k,v in attrs.items()]
        line = [self.start(attrs), cgi.escape(text), self.end()]
        return ElementContext(self, line)
            
    def __enter__(self):
        document.add([self.start()])
        document.indent += 1
        
    def __exit__(self, *error):
        document.indent -= 1
        document.add([self.end()])

class ElementContext(object):
    def __init__(self, element, firstline):
        self.element = element
        self.firstline = firstline
        document.add(self.firstline)
    
    def __call__(self, text):
        assert document.lines[-1][1] is self.firstline, "Can't add text to an element after adding other elements to the document."
        self.firstline[1] = text
        return self
        
    def __enter__(self):
        assert document.lines[-1][1] is self.firstline, "Can't use an element as a context manager after adding other elements to the document."
        self.firstline.pop()
        document.indent += 1
        
    def __exit__(self, *error):
        document.indent -= 1
        document.add([self.element.end()])
